﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.ComponentModel;

namespace BrainTechLLC
{
    [Serializable]
#if NO_SILVERLIGHT
    [Browsable(true)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
#endif
    [DataContract]
    public class Vector4BaseFloat : IVector4Float
    {
        protected float x, y, z, w;

        [XmlIgnore]
        public float X { get { return x; } set { x = value; } }

        [XmlIgnore]
        public float Y { get { return y; } set { y = value; } }

        [XmlIgnore]
        public float Z { get { return z; } set { z = value; } }

        [XmlIgnore]
        public float W { get { return w; } set { w = value; } }

        public Vector4BaseFloat() { }
        public Vector4BaseFloat(Vector4BaseFloat vector) { Set(vector.X, vector.Y, vector.Z, vector.W); }
        public Vector4BaseFloat(float x1, float y1, float z1, float w1) { Set(x1, y1, z1, w1); }
        public Vector4BaseFloat(float x1, float y1, float z1) { Set(x1, y1, z1); }
        public Vector4BaseFloat(Vector4BaseFloat v1, Vector4BaseFloat v2)
        {
            x = v2.x - v1.x;
            y = v2.y - v1.y;
            z = v2.z - v1.z;
            w = v2.w - v1.w;
        }

        public override bool Equals(object obj)
        {
            Vector4BaseFloat v = obj as Vector4BaseFloat;
            if (v == null) return false;
            return x == v.x && y == v.y && z == v.z && w == v.w;
        }

        [XmlIgnore]
        public float this[int CoordinateIndex]
        {
            get
            {
                switch (CoordinateIndex)
                {
                    case 0: return x;
                    case 1: return y;
                    case 2: return z;
                    case 3: return w;
                }

                return 0;
            }
            set
            {
                switch (CoordinateIndex)
                {
                    case 0: x = value; break;
                    case 1: y = value; break;
                    case 2: z = value; break;
                    case 3: w = value; break;
                }
            }
        }
        public static Vector4BaseFloat operator *(Vector4BaseFloat V, float Factor)
        {
            return new Vector4BaseFloat(V.x * Factor, V.y * Factor, V.z * Factor, V.w * Factor);
        }

        public static Vector4BaseFloat operator /(Vector4BaseFloat V, float Divider)
        {
            if (Divider == 0) throw new ArgumentException("Divider cannot be 0");
            return new Vector4BaseFloat(V.x / Divider, V.y / Divider, V.z / Divider, V.w / Divider);
        }

        public void Set(float x1, float y1, float z1, float w1) { x = x1; y = y1; z = z1; w = w1; }
        public void Set(float x1, float y1, float z1) { x = x1; y = y1; z = z1; }
        public void Set(IVector4Float vect) { x = vect.X; y = vect.Y; z = vect.Z; w = vect.W; }

        public static float Length2D(Vector4BaseFloat v) { return v.Length2D(); }
        public static float Length3D(Vector4BaseFloat v) { return v.Length3D(); }
        public static float Length4D(Vector4BaseFloat v) { return v.Length4D(); }

        public static bool operator ==(Vector4BaseFloat o1, Vector4BaseFloat o2)
        {
            object oA = (object)o1;
            object oB = (object)o2;
            if (oA == null && oB == null) return true;
            if ((oA == null && oB != null) || (oA != null && oB == null)) return false;
            return (o1.x == o2.x &&
                      o1.y == o2.y &&
                      o1.z == o2.z &&
                      o1.w == o2.w);
        }

        public bool SameIntLocation(IVector4Float other)
        {
            return (int)x == other.XInt &&
                      (int)y == other.YInt &&
                      (int)z == other.ZInt &&
                      (int)w == other.WInt;
        }

        public int XInt { get { return (int)x; } }
        public int YInt { get { return (int)y; } }
        public int ZInt { get { return (int)z; } }
        public int WInt { get { return (int)w; } }

        public static bool operator !=(Vector4BaseFloat o1, Vector4BaseFloat o2)
        {
            object oA = (object)o1;
            object oB = (object)o2;
            if (oA == null && oB == null) return false;
            if ((oA == null && oB != null) || (oA != null && oB == null)) return true;
            return !(o1.x == o2.x &&
                        o1.y == o2.y &&
                        o1.z == o2.z &&
                        o1.w == o2.w);
        }

        public float Length2D()
        {
            return (float)Math.Sqrt((x * x) + (y * y));
        }

        public float Length3D()
        {
            return (float)Math.Pow((x * x) + (y * y) + (z * z), 0.333333333333333334);
        }

        public float Length4D()
        {
            return (float)Math.Pow((x * x) + (y * y) + (z * z) + (w * w), 0.25);
        }

        public static Vector4BaseFloat Normalize2D(Vector4BaseFloat v)
        {
            float f = v.Length2D();
            return new Vector4BaseFloat(v.X / f, v.Y / f, 0);
        }

        public static Vector4BaseFloat Normalize3D(Vector4BaseFloat v)
        {
            float f = v.Length3D();
            return new Vector4BaseFloat(v.X / f, v.Y / f, v.Z / f);
        }

        public static Vector4BaseFloat Normalize4D(Vector4BaseFloat v)
        {
            float f = v.Length4D();
            return new Vector4BaseFloat(v.X / f, v.Y / f, v.Z / f, v.W / f);
        }

        public Vector4BaseFloat Normalize2D()
        {
            float f = this.Length2D();
            this.X = (float)(this.X / f);
            this.Y = (float)(this.Y / f);
            return this;
        }

        public Vector4BaseFloat Normalize3D()
        {
            float f = this.Length3D();
            this.X = (float)(this.X / f);
            this.Y = (float)(this.Y / f);
            this.Z = (float)(this.Z / f);
            return this;
        }

        public Vector4BaseFloat Normalize4D()
        {
            float f = this.Length4D();
            this.X = (float)(this.X / f);
            this.Y = (float)(this.Y / f);
            this.Z = (float)(this.Z / f);
            this.W = (float)(this.W / f);
            return this;
        }

        public static Vector4BaseFloat operator -(Vector4BaseFloat v1, Vector4BaseFloat v2)
        {
            return new Vector4BaseFloat(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z, v1.W - v2.W);
        }

        public static float Dot(Vector4BaseFloat v1, Vector4BaseFloat v2)
        {
            return (v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z + v1.W * v2.W);
        }

        public static Vector4BaseFloat Truncate2D(Vector4BaseFloat vec, float max_value)
        {
            if (vec.Length2D() > max_value)
            {
                Vector4BaseFloat newVec = new Vector4BaseFloat(vec).Normalize2D();
                return newVec * max_value;
            }
            return vec;
        }

        public static Vector4BaseFloat Truncate3D(Vector4BaseFloat vec, float max_value)
        {
            if (vec.Length3D() > max_value)
            {
                Vector4BaseFloat newVec = new Vector4BaseFloat(vec).Normalize3D();
                return newVec * max_value;
            }
            return vec;
        }

        public static Vector4BaseFloat Truncate4D(Vector4BaseFloat vec, float max_value)
        {
            if (vec.Length4D() > max_value)
            {
                Vector4BaseFloat newVec = new Vector4BaseFloat(vec).Normalize4D();
                return newVec * max_value;
            }
            return vec;
        }

        /// <summary>
        /// Scalar product between two vectors.
        /// </summary>
        /// <param name="V1">First vector.</param>
        /// <param name="V2">Second vector.</param>
        /// <returns>Value resulting from the scalar product.</returns>
        public static float operator |(Vector4BaseFloat V1, Vector4BaseFloat V2)
        {
            float ScalarProduct = 0;
            for (int i = 0; i < 4; i++) ScalarProduct += V1[i] * V2[i];
            return ScalarProduct;
        }

        /// <summary>
        /// Returns a point resulting from the translation of a specified point.
        /// </summary>
        /// <param name="P">Point to translate.</param>
        /// <param name="V">Vector to apply for the translation.</param>
        /// <returns>Point resulting from the translation.</returns>
        public static Vector4BaseFloat operator +(Vector4BaseFloat P, Vector4BaseFloat V)
        {
            Vector4BaseFloat result = new Vector4BaseFloat();
            for (int i = 0; i < 4; i++) result[i] = P[i] + V[i];
            return result;
        }

        /// <summary>
        /// Returns the projection of a point on the line defined with two other points.
        /// When the projection is out of the segment, then the closest extremity is returned.
        /// </summary>
        /// <exception cref="ArgumentNullException">None of the arguments can be null.</exception>
        /// <exception cref="ArgumentException">P1 and P2 must be different.</exception>
        /// <param name="Pt">Point to project.</param>
        /// <param name="P1">First point of the line.</param>
        /// <param name="P2">Second point of the line.</param>
        /// <returns>The projected point if it is on the segment / The closest extremity otherwise.</returns>
        public static Vector4BaseFloat ProjectOnLine(Vector4BaseFloat Pt, Vector4BaseFloat P1, Vector4BaseFloat P2)
        {
            if (Pt == null || P1 == null || P2 == null) throw new ArgumentNullException("None of the arguments can be null.");
            if (P1.Equals(P2)) throw new ArgumentException("P1 and P2 must be different.");
            Vector4BaseFloat VLine = new Vector4BaseFloat(P1, P2);
            Vector4BaseFloat V1Pt = new Vector4BaseFloat(P1, Pt);
            // 3D correct below?
            Vector4BaseFloat Translation = VLine * (VLine | V1Pt) / VLine.SquareNorm3D;
            Vector4BaseFloat Projection = P1 + Translation;

            Vector4BaseFloat V1Pjt = new Vector4BaseFloat(P1, Projection);
            float D1 = V1Pjt | VLine;
            if (D1 < 0) return P1;

            Vector4BaseFloat V2Pjt = new Vector4BaseFloat(P2, Projection);
            float D2 = V2Pjt | VLine;
            if (D2 > 0) return P2;

            return Projection;
        }

        public static float DistanceBetween2D(Vector4BaseFloat P1, Vector4BaseFloat P2)
        {
            return (float)Math.Sqrt((P1.X - P2.X) * (P1.X - P2.X) + (P1.Y - P2.Y) * (P1.Y - P2.Y));
        }

        public static Vector4BaseFloat Add(Vector4BaseFloat v1, Vector4BaseFloat v2)
        {
            return v1 + v2;
        }

        public static Vector4BaseFloat Subtract(Vector4BaseFloat v1, Vector4BaseFloat v2)
        {
            return v1 - v2;
        }

        public static Vector4BaseFloat Multiply(Vector4BaseFloat v1, float f)
        {
            return v1 * f;
        }

        public static Vector4BaseFloat Divide(Vector4BaseFloat v1, float f)
        {
            return v1 / f;
        }

        public override int GetHashCode()
        {
            float HashCode = 0;
            for (int i = 0; i < 4; i++) HashCode += this[i];
            return (int)HashCode;
        }

        public float SquareNorm3D
        {
            get
            {
                float Sum = 0;
                for (int i = 0; i < 3; i++) Sum += this[i] * this[i];
                return Sum;
            }
        }

        public float SquareNorm4D
        {
            get
            {
                float Sum = 0;
                for (int i = 0; i < 4; i++) Sum += this[i] * this[i];
                return Sum;
            }
        }

        [XmlIgnore]
        public float Norm3D
        {
            get { return (float)Math.Sqrt(SquareNorm3D); }
            set
            {
                float N = Norm3D;
                if (N == 0) throw new InvalidOperationException("Cannot set norm for a null vector");
                if (N != value)
                {
                    float Facteur = value / N;
                    for (int i = 0; i < 3; i++) this[i] *= Facteur;
                }
            }
        }

        [XmlIgnore]
        public float Norm4D
        {
            get { return (float)Math.Sqrt(SquareNorm4D); }
            set
            {
                float N = Norm4D;
                if (N == 0) throw new InvalidOperationException("Cannot set norm for a null vector");
                if (N != value)
                {
                    float Facteur = value / N;
                    for (int i = 0; i < 4; i++) this[i] *= Facteur;
                }
            }
        }
    }
}
